package core.file;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import core.config.Factory;

/**
 * 改行テキスト出力(Zip)
 * @author Tadashi Nakayama
 */
public final class ZippedLineOutputStream extends FilterOutputStream {

	/** ページ内最大数 */
	private int line = -1;
	/** エントリ名 */
	private String name = null;
	/** ページ内カウント */
	private int count = -1;
	/** エントリ数 */
	private int entries = 0;
	/** 直前出力文字 */
	private int prev = 0;
	/** ページタイトル */
	private byte[] title;

	/**
	 * コンストラクタ
	 * @param os OutputStream
	 */
	public ZippedLineOutputStream(final OutputStream os) {
		super(getZipOutputStream(os));
	}

	/**
	 * コンストラクタ
	 * @param os OutputStream
	 * @param val Charset
	 */
	public ZippedLineOutputStream(final OutputStream os, final Charset val) {
		super(getZipOutputStream(os, val));
	}

	/**
	 * ZipOutputStream取得
	 * @param os OutputStream
	 * @return ZipOutputStream
	 */
	private static ZipOutputStream getZipOutputStream(final OutputStream os) {
		return Factory.construct(Factory.getConstructor(
				ZipOutputStream.class, OutputStream.class), os);
	}

	/**
	 * ZipOutputStream取得
	 * @param os OutputStream
	 * @param val Charset
	 * @return ZipOutputStream
	 */
	private static ZipOutputStream getZipOutputStream(final OutputStream os, final Charset val) {
		return Factory.construct(Factory.getConstructor(
				ZipOutputStream.class, OutputStream.class, Charset.class), os, val);
	}

	/**
	 * @see java.io.FilterOutputStream#write(int)
	 */
	@Override
	public void write(final int b) throws IOException {
		if (this.line == this.count) {
			putNextEntry(true);
		} else if (!hasEntry()) {
			putNextEntry(false);
		}

		super.out.write(b);

		if (this.prev == '\r' && b == '\n') {
			if (1 < this.line) {
				this.count++;
			}
		}
		this.prev = b;
	}

	/**
	 * ページ内最大数設定
	 * @param val ページ内最大数
	 */
	public void setLine(final int val) {
		this.line = val;
	}

	/**
	 * ページタイトル設定
	 * @param val ページタイトル（改行付）
	 */
	public void setTitle(final byte[] val) {
		this.title = null;
		if (val != null && 0 < val.length) {
			this.title = val.clone();
		}
	}

	/**
	 * エントリ名設定
	 * @param val エントリ名
	 */
	public void setEntryName(final String val) {
		this.name = val.replaceAll("[\\\\/:\\*\\?\"<>\\|：￥／？＊［］\\[\\]]", "").trim();
		this.entries = 0;
	}

	/**
	 * エントリ終了
	 * @throws IOException IO例外
	 */
	public void finish() throws IOException {
		if (hasEntry()) {
			getZipStream().closeEntry();
			getZipStream().finish();
		}
	}

	/**
	 * エントリ設定
	 * @param flg タイトル出力フラグ
	 * @throws IOException IO例外
	 */
	public void putNextEntry(final boolean flg) throws IOException {
		if (hasEntry()) {
			getZipStream().closeEntry();
		}

		getZipStream().putNextEntry(getEntry(this.entries));
		outputTitle(flg);

		this.count = 0;
		this.entries++;
	}

	/**
	 * タイトル出力
	 * @param flg タイトル出力フラグ
	 * @throws IOException IO例外
	 */
	private void outputTitle(final boolean flg) throws IOException {
		if (this.title != null && flg) {
			super.out.write(this.title);
		}
	}

	/**
	 * エントリ存在確認
	 * @return エントリが存在した場合 true を返す。
	 */
	private boolean hasEntry() {
		return 0 <= this.count;
	}

	/**
	 * ZipOutputStream取得
	 * @return ZipOutputStream
	 */
	private ZipOutputStream getZipStream() {
		return ZipOutputStream.class.cast(super.out);
	}

	/**
	 * ZipEntry取得
	 * @param entry エントリ数
	 * @return ZipEntry
	 */
	private ZipEntry getEntry(final int entry) {
		String str = this.name;
		int loc = str.lastIndexOf('.');
		if (0 <= loc) {
			str = str.substring(0, loc).trim() + addEntry(entry) + str.substring(loc);
		} else {
			str = str + addEntry(entry);
		}
		return new ZipEntry(str);
	}

	/**
	 * エントリ数加算
	 * @param entry エントリ数
	 * @return エントリ数
	 */
	private String addEntry(final int entry) {
		return 0 < entry ? "_" + (entry + 1) : "";
	}
}
